package builder.trainer;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;

import builder.trainer.counter.Attribute;
import builder.trainer.counter.AttributeValue;
import builder.trainer.counter.Counter;
import builder.trainer.data.DataManager;
import builder.tree.DecisionNode;
import builder.tree.Node;

public class Trainer {

	private int objColumn;
	private Counter counter;
	private ArrayList<Node> nodes;

	private int sum;

	public Trainer() {
		
	}

	public ArrayList<Node> train(DataManager dm, int objColumn) {
		Counter counter = new Counter(dm);
		counter.countAll();

		this.nodes = new ArrayList<Node>();
		this.counter = counter;
		this.objColumn = (objColumn);
		Attribute attr = this.select();

		Node root = new Node("root", "root");
		root.setObjColumn(this.objColumn);
		root.setChildrenAttrName(attr.getAttributeName());
		root.setChildrenAttrColumn(attr.getIndex());

		this.nodes.add(root);
		this.deepIn(attr);
		return this.nodes;
	}

	public void deepIn(Attribute attr) {
		Set<Entry<String, AttributeValue>> entries = attr.getAttributeValues()
				.entrySet();
		for (Entry<String, AttributeValue> entry : entries) {
			AttributeValue value = entry.getValue();
			counter.count(value.getIndexes());

			sum = 0;
			Attribute selected = this.select();
			if (sum == this.getCounter().getAttributes().length - 2) {
				DecisionNode hNode = this.createDecisionNode(
						attr.getAttributeName(), value);
				hNode.setObjColumn(this.getObjColumn());
				getNodes().add(hNode);
				continue;
			}

			Node node = new Node(attr.getAttributeName(), value.getValueName());
			node.setObjColumn(this.getObjColumn());
			getNodes().add(node);
			node.setChildrenAttrName(selected.getAttributeName());
			node.setChildrenAttrColumn(selected.getIndex());
			deepIn(selected);
		}
	}

	private DecisionNode createDecisionNode(String attributeName,
			AttributeValue value) {
		String sValue = value.getValueName();
		DecisionNode hNode = new DecisionNode(attributeName, sValue);
		Attribute attr = this.counter.getAttributes()[this.getObjColumn()];
		int total = value.getIndexes().size();
		Set<Entry<String, AttributeValue>> entries = attr.getAttributeValues()
				.entrySet();
		for (Entry<String, AttributeValue> entry : entries) {
			double p = entry.getValue().getIndexes().size() * 1.0 / total;
			DecimalFormat r = new DecimalFormat();
			r.applyPattern("#.0000");
			p = Double.parseDouble(r.format(p));
			hNode.addDecision(entry.getKey(), p);
		}
		return hNode;
	}

	// private DecisionNode createDecisionNode(String attrName,
	// AttributeValue value) {
	// String vname = value.getValueName();
	// // for(int i:value.getIndexes()){
	// // System.out.print(i+",");
	// // }
	// // System.out.println();
	// String decision =
	// this.counter.getRecord(value.getIndexes().get(0))[this.getObjColumn()];
	// DecisionNode dNode = new DecisionNode(attrName, vname, decision);
	// return dNode;
	// }

	public Attribute select() {
		int selected = 0;
		double selectedEntropy = 1000;
		for (int i = 0; i < getCounter().getAttributes().length; i++) {
			if (i == this.getObjColumn())
				continue;
			Attribute attr = getCounter().getAttributes()[i];
			double entropy = this.calInfoEntropy(attr.getAttributeValues(),
					getCounter().getRecordsNum());
			attr.setEntropy(entropy);
			// System.out.println(entropy+"###");
			if (entropy == selectedEntropy) {
				sum++;
			}
			if (entropy < selectedEntropy) {
				selectedEntropy = entropy;
				selected = i;
			}
		}
		return counter.getAttributes()[selected];
	}

	public double calObjInfoEntropy(Attribute attribute, int attrCount) {
		double entropy = 0;
		HashMap<String, AttributeValue> values = attribute.getAttributeValues();
		Set<Entry<String, AttributeValue>> entries = values.entrySet();
		for (Entry<String, AttributeValue> e : entries) {
			AttributeValue value = e.getValue();
			entropy += this.calEntropy(value.getCount(), attrCount, 2);
		}
		return 0 - entropy;
	}

	private double calInfoEntropy(HashMap<String, AttributeValue> values,
			int attrCount) {
		double sInfoEntropy = 0;
		Set<Entry<String, AttributeValue>> entries = values.entrySet();
		for (Entry<String, AttributeValue> e : entries) {
			AttributeValue value = e.getValue();
			double entropy = this.calInfoEntropy(value);
			value.setEntropy(entropy);
			sInfoEntropy += value.getCount() * entropy / attrCount;
		}
		return sInfoEntropy;
	}

	private double calInfoEntropy(AttributeValue value) {
		HashMap<String, Integer> objValues = value
				.getObjectValues(getObjColumn());
		double infoEntropy = 0;
		Set<Entry<String, Integer>> entries = objValues.entrySet();
		for (Entry<String, Integer> e : entries) {
			int num = e.getValue();
			infoEntropy += this.calEntropy(num, value.getCount(), 2);
		}
		return 0 - infoEntropy;
	}

	private double calEntropy(int objValueCount, int count, int valueTypeCount) {
		double entropy = 0;
		double ratio = objValueCount * 1.0 / count;
		entropy = ratio * Math.log(ratio) / Math.log(1.0 * valueTypeCount);
		return entropy;
	}

	public void setCounter(Counter counter) {
		this.counter = counter;
	}

	public Counter getCounter() {
		return counter;
	}

	public ArrayList<Node> getNodes() {
		return nodes;
	}

	public int getObjColumn() {
		return objColumn;
	}
}
